#include "stdafx.h"

namespace classifier_test
{

repro_classifier::repro_classifier()
{
}

repro_classifier::repro_classifier(const repro_classifier& Val)
	: stream_classifier(Val)
{
	WinSize = Val.WinSize;
	StLearnSize = Val.StLearnSize;
	TrigErrTh = Val.TrigErrTh;
	ProbTh = Val.ProbTh;
	AccuTh = Val.AccuTh;
	EquTh = Val.EquTh;
}

smart_ptr<stream_classifier> repro_classifier::clone() const
{
	return make_smart_ptr(new repro_classifier(*this));
}

int repro_classifier::get_window_size() const
{
	return WinSize;
}

void repro_classifier::set_window_size(int Val)
{
	WinSize = Val;
}

int repro_classifier::get_stable_learning_size() const
{
	return StLearnSize;
}

void repro_classifier::set_stable_learning_size(int Val)
{
	StLearnSize = Val;
}

double repro_classifier::get_trigger_error_threshold() const
{
	return TrigErrTh;
}

void repro_classifier::set_trigger_error_threshold(double Val)
{
	TrigErrTh = Val;
}

double repro_classifier::get_probability_threshold() const
{
	return ProbTh;
}

void repro_classifier::set_probability_threshold(double Val)
{
	ProbTh = Val;
}

double repro_classifier::get_accuracy_threshold() const
{
	return AccuTh;
}

void repro_classifier::set_accuracy_threshold(double Val)
{
	AccuTh = Val;
}

double repro_classifier::get_equivalence_threshold() const
{
	return EquTh;
}

void repro_classifier::set_equivalence_threshold(double Val)
{
	EquTh = Val;
}

void repro_classifier::build(subarray<const stream_record* const> Data)
{
	TmpTrigs.assign(1, 0);
	TmpClassifiers.resize(1);
	WinBegin = 0;
	CurrClassifier = 0;
	LastConcept = 0;
	stream_classifier::train(Data);
}

void repro_classifier::train(const stream_record& Record)
{
	assert(Domain.valid(Record));
	TrData.push_back(smart_ptr<stream_record>(new stream_record(Record)));
	TrHits.push_back(CurrClassifier != 0 && CurrClassifier->classify(Record) == Record.back().discr);

	if (detect_trigger())
	{
		if (WinBegin < StLearnSize) add_tmp_trigger(WinBegin);
		else add_stable_trigger(WinBegin);
		CurrClassifier = find_next_classifier();
		WinBegin = TrData.size();
	}
	if (TmpTrigs.size() > 1 && (int)TrData.size() >= StLearnSize)
	{
		add_stable_trigger(TrData.size());
		CurrClassifier = &*LastConcept->Classifier;
		WinBegin = TrData.size();
	}
}

void repro_classifier::estimate(const stream_record& Record, array<double>& Probs) const
{
	if (CurrClassifier == 0) Probs.assign(ClassCount, 0);
	else CurrClassifier->estimate(Record, Probs);
	normalize_probabilities(Probs);
}

int repro_classifier::get_concept_count() const
{
	return Concepts.size();
}

void repro_classifier::initialize_options(command_line_options& Options) const
{
	stream_classifier::initialize_options(Options);
	Options.add("window-size").add_name("wz").set_argument("<count>").set_info("# of records in the recent trigger window, default 20");
	Options.add("stable-learning-size").add_name("slz").set_argument("<count>").set_info("# of records used to perform a stable learning, default 200");
	Options.add("trigger-error-threshold").add_name("tet").set_argument("<value>").set_info("Concept change is detected when the error rate of the trigger window exceeds <value>, default 0.2");
	Options.add("probability-threshold").add_name("pt").set_argument("<value>").set_info("The minimum frequency required to find a next concept according to historical concept transitions, default 0.8");
	Options.add("accuracy-threshold").add_name("at").set_argument("<value>").set_info("The minimum accuracy required to find a concept from historical concepts, default 0.8");
	Options.add("equivalence-threshold").add_name("et").set_argument("<value>").set_info("The minimum similarity required to consider two concepts equivalent, default 0.8");
}

void repro_classifier::apply_arguments(const command_line_arguments& Args)
{
	stream_classifier::apply_arguments(Args);
	WinSize = to_int(Args["wz"].get_or_default("20"));
	StLearnSize = to_int(Args["slz"].get_or_default("200"));
	TrigErrTh = to_double(Args["tet"].get_or_default("0.2"));
	ProbTh = to_double(Args["pt"].get_or_default("0.8"));
	AccuTh = to_double(Args["at"].get_or_default("0.8"));
	EquTh = to_double(Args["et"].get_or_default("0.8"));
}

bool repro_classifier::detect_trigger()
{
	if ((int)TrData.size() - WinBegin > WinSize) WinBegin = TrData.size() - WinSize;
	if ((int)TrData.size() - WinBegin < WinSize || TrHits[WinBegin]) return false;
	int Errs = 0;
	for (int I = WinBegin; I < (int)TrHits.size(); I++) if (!TrHits[I]) Errs++;
	return (double)Errs / WinSize >= TrigErrTh;
}

void repro_classifier::add_tmp_trigger(int Pos)
{
	if (Pos == (int)TmpTrigs.back()) return;
	smart_ptr<classifier> C = BaseClassifier->clone();
	C->train(make_subarray_to(TrData, TmpTrigs.back(), Pos).convert<record*>());
	TmpTrigs.push_back(Pos);
	TmpClassifiers.back() = C;
	TmpClassifiers.push_back(C);
}

void repro_classifier::add_stable_trigger(int Pos)
{
	add_tmp_trigger(Pos);
	smart_ptr<classifier> C = BaseClassifier->clone();
	C->train(make_subarray(TrData, 0, Pos).convert<record*>());
	double Accurs = 0;
	/*for (int I = 0; I < (int)TmpClassifiers.size() - 1; I++)
		Accurs += accuracy_rate(&*TmpClassifiers[I], TmpTrigs[I], TmpTrigs[I + 1]) * (TmpTrigs[I + 1] - TmpTrigs[I]);
	Accurs /= Pos;*/
	for (int I = 0; I < (int)TmpClassifiers.size() - 1; I++)
		Accurs += accuracy_rate(&*TmpClassifiers[I], 0, Pos);
	Accurs /= TmpClassifiers.size() - 1;
	if (accuracy_rate(&*C, 0, Pos, Accurs) >= Accurs)
		add_concept(C);
	else
		for (int I = 0; I < (int)TmpClassifiers.size() - 1; I++)
			add_concept(TmpClassifiers[I]);
	TrData.erase(TrData.begin(), TrData.begin() + Pos);
	TrHits.erase(TrHits.begin(), TrHits.begin() + Pos);
	TmpTrigs.assign(1, 0);
	TmpClassifiers.clear();
	TmpClassifiers.resize(1);
	WinBegin -= Pos;
	if (WinBegin < 0) WinBegin = 0;
}

void repro_classifier::add_concept(smart_ptr<classifier> Classifier)
{
	concept* Concept = find_equivalent_concept(&*Classifier);
	if (Concept == 0)
	{
		smart_ptr<concept> V(Concept = new concept);
		Concepts[Concept] = V;
		Concept->ShiftCount = 0;
		Concept->Classifier = Classifier;
	}
	if (LastConcept != 0 && Concept != LastConcept)
	{
		Concepts[LastConcept]->ShiftCounts[Concept]++;
		Concepts[LastConcept]->ShiftCount++;
	}
	LastConcept = Concept;
}

repro_classifier::concept* repro_classifier::find_equivalent_concept(classifier* Classifier)
{
	int Start = (int)TrData.size() - WinSize;
	assert(Start >= 0);
	vector<int> CResult((int)TrData.size() - Start);
	for (int I = 0; I < (int)CResult.size(); I++)
		CResult[I] = Classifier->classify(*TrData[Start + I]);
	concept* Other = 0;
	double Score = -1;
	for (concept_map::iterator It = Concepts.begin(); It != Concepts.end(); ++It)
	{
		double Temp = equivalence_score(CResult, &*It->first->Classifier, Start, TrData.size(), max(EquTh, Score));
		if (Temp > Score)
		{
			Score = Temp;
			Other = It->first;
		}
	}
	if (Score < EquTh) return 0;
	return Other;
}

classifier* repro_classifier::find_next_classifier()
{
	vector<classifier*> Cands;
	classifier* Cand;
	double Accur;
	assert((int)TrData.size() - WinBegin >= WinSize);
	if (LastConcept != 0 && LastConcept->ShiftCount > 0)
		for (map<concept*, int>::iterator It = LastConcept->ShiftCounts.begin(); It != LastConcept->ShiftCounts.end(); ++It)
			if ((double)It->second / LastConcept->ShiftCount >= ProbTh)
				Cands.push_back(&*It->first->Classifier);
	if (Cands.empty())
	{
		for (int I = 0; I < (int)TmpClassifiers.size() - 1; I++)
			Cands.push_back(&*TmpClassifiers[I]);
		for (concept_map::iterator It = Concepts.begin(); It != Concepts.end(); ++It)
			Cands.push_back(&*It->first->Classifier);
	}
	Cand = 0;
	Accur = 0;
	for (int I = 0; I < (int)Cands.size(); I++)
	{
		double Temp = accuracy_rate(Cands[I], WinBegin, TrData.size(), max(Accur, AccuTh));
		if (Temp > Accur) Accur = Temp, Cand = Cands[I];
	}
	if (Cand == 0 || Accur < AccuTh)
	{
		/*add_tmp_trigger(TrData.size());
		Cand = &*TmpClassifiers[TmpClassifiers.size() - 2];*/
		smart_ptr<classifier> C = BaseClassifier->clone();
		C->train(make_subarray_to(TrData, WinBegin, TrData.size()).convert<record*>());
		Cand = &*C;
		TmpClassifiers.back() = C;
	}
	return Cand;
}

double repro_classifier::accuracy_rate(classifier* Classifier, int Begin, int End, double LBound) const
{
	int Accurs = 0;
	LBound *= (End - Begin);
	for (int I = Begin; I < End; I++)
		if (Classifier->classify(*TrData[Begin]) == TrData[Begin]->back().discr)
			Accurs++;
		else if (Accurs + (End - I - 1) < LBound)
			return 0;
	return (double)Accurs / (End - Begin);
}

double repro_classifier::equivalence_score(const vector<int>& AResult, classifier* BClassifier, int Begin, int End, double LBound) const
{
	int Score = 0;
	assert((int)AResult.size() == End - Begin);
	LBound *= End - Begin;
	for (int I = 0; I < End - Begin; I++)
		if (AResult[I] == BClassifier->classify(*TrData[Begin + I])) Score++;
		else if (--Score + ((int)AResult.size() - I - 1) < LBound) return -1;
	return (double)Score / (End - Begin);
}

}
